﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ImageProcessingLib
{
    #region === Brightness ===
    /// <summary>
    /// Яркость.
    /// </summary>
    class Brightness : AbstractImageEffect
    {
        readonly int _rShift, _gShift, _bShift;

        public Brightness(BitmapInfo4 bi, int rShift, int gShift, int bShift)
        {
            _bitmapInfo = bi;
            _rShift = rShift;
            _gShift = gShift;
            _bShift = bShift;
        }

        protected override int PixelEffect(int index)
        {
            int color = _bitmapInfo.ImageArray4[index];
            Utils.Unpack(color, out int a, out int r, out int g, out int b);
            return Utils.CutEdges(a, r + _rShift, g + _gShift, b + _bShift);
        }
    }
    #endregion

    #region === Desaturate ===
    /// <summary>
    /// Оттенки серого.
    /// </summary>
    class Desaturate : AbstractImageEffect
    {
        public Desaturate(BitmapInfo4 bi)
        {
            _bitmapInfo = bi;
        }

        protected override int PixelEffect(int index)
        {
            int color = _bitmapInfo.ImageArray4[index];
            Utils.Unpack(color, out int a, out int r, out int g, out int b);
            int mean = Convert.ToInt32(0.2125 * r + 0.7154 * g + 0.0721 * b);

            return Utils.CutEdges(a, mean, mean, mean);
        }
    }
    #endregion

    #region === Saturate ===
    /// <summary>
    /// Насыщенность.
    /// </summary>
    class Saturate : AbstractImageEffect
    {
        readonly float _parameter;

        public Saturate(BitmapInfo4 bi, float t)
        {
            _bitmapInfo = bi;
            _parameter = t;
        }

        protected override int PixelEffect(int index)
        {
            int color = _bitmapInfo.ImageArray4[index];
            Utils.Unpack(color, out int a, out int r, out int g, out int b);

            int mean = Convert.ToInt32(0.2125 * r + 0.7154 * g + 0.0721 * b);

            r = Convert.ToInt32((r - mean) * _parameter + mean);
            g = Convert.ToInt32((g - mean) * _parameter + mean);
            b = Convert.ToInt32((b - mean) * _parameter + mean);

            return Utils.CutEdges(a, r, g, b);
        }
    }
    #endregion

    #region === Contract ===
    /// <summary>
    /// Контрастность.
    /// </summary>
    class Contrast : AbstractImageEffect
    {
        readonly float _parameter;

        public Contrast(BitmapInfo4 bi, float t)
        {
            _bitmapInfo = bi;
            _parameter = t;
        }

        protected override int PixelEffect(int index)
        {
            int color = _bitmapInfo.ImageArray4[index];
            Utils.Unpack(color, out int a, out int r, out int g, out int b);

            const float mean = 128;

            r = Convert.ToInt32((r - mean) * _parameter + mean);
            g = Convert.ToInt32((g - mean) * _parameter + mean);
            b = Convert.ToInt32((b - mean) * _parameter + mean);

            return Utils.CutEdges(a, r, g, b);
        }
    }
    #endregion

    #region === Diffuse ===
    /// <summary>
    /// Диффузное размытие.
    /// </summary>
    class Diffuse : AbstractImageEffect
    {
        readonly int _parameter;
        readonly Random _rnd;

        public Diffuse(BitmapInfo4 bi, int size)
        {
            _bitmapInfo = bi;
            _parameter = size;
            _rnd = new Random();
        }

        protected override int PixelEffect(int index)
        {
            int line = _bitmapInfo.Stride / 4;

            int x = index % line;
            int y = index / line;

            int rdx = _rnd.Next(2 * _parameter + 1);
            int rdy = _rnd.Next(2 * _parameter + 1);

            return GetPixel(x - _parameter + rdx, y - _parameter + rdy);
        }
    }
    #endregion

    #region === Invert ===
    /// <summary>
    /// Инвертирование изображения.
    /// </summary>
    class Invert : AbstractImageEffect
    {
        public Invert(BitmapInfo4 bi)
        {
            _bitmapInfo = bi;
        }

        protected override int PixelEffect(int index)
        {
            int color = _bitmapInfo.ImageArray4[index];
            Utils.Unpack(color, out int a, out int r, out int g, out int b);

            return Utils.Pack(a, 255 - r, 255 - g, 255 - b);
        }
    }
    #endregion

    #region === Hue ===
    /// <summary>
    /// Оттенки цвета.
    /// </summary>
    class Hue : AbstractImageEffect
    {
        readonly System.Drawing.Color _color;
        byte[] _R, _G, _B;

        public Hue(BitmapInfo4 bi, int r, int g, int b)
        {
            _bitmapInfo = bi;
            _color = System.Drawing.Color.FromArgb(r, g, b);
            PrepareHueArray();
        }

        private void PrepareHueArray()
        {
            const int cColorsCount = 256;
            _R = new byte[cColorsCount];
            _G = new byte[cColorsCount];
            _B = new byte[cColorsCount];

            float r = _color.R;
            float g = _color.G;
            float b = _color.B;

            double distanceToBlack = Math.Sqrt(r * r + g * g + b * b);
            double distanceToWhite = Math.Sqrt((r - 255) * (r - 255) + (g - 255) * (g - 255) + (b - 255) * (b - 255));

            int x = Convert.ToInt32((distanceToBlack * cColorsCount) / (distanceToBlack + distanceToWhite));

            if (x < 2)
            {
                x = 2;
            }
            else if (x > 254)
            {
                x = 254;
            }

            int y = cColorsCount - x;

            for (int i = 0; i < x; i++)
            {
                _R[i] = Convert.ToByte((r * i) / (x - 1));
                _G[i] = Convert.ToByte((g * i) / (x - 1));
                _B[i] = Convert.ToByte((b * i) / (x - 1));
            }

            for (int i = 1; i <= y; i++)
            {
                _R[i - 1 + x] = Convert.ToByte(((255 - r) * i) / y + r);
                _G[i - 1 + x] = Convert.ToByte(((255 - g) * i) / y + g);
                _B[i - 1 + x] = Convert.ToByte(((255 - b) * i) / y + b);
            }
        }

        protected override int PixelEffect(int index)
        {
            int color = _bitmapInfo.ImageArray4[index];
            Utils.Unpack(color, out int a, out int r, out int g, out int b);

            int mean = Convert.ToInt32(0.2125 * r + 0.7154 * g + 0.0721 * b);

            return Utils.Pack(a, _R[mean], _G[mean], _B[mean]);
        }
    }
    #endregion

    #region === Gamma correction ===
    /// <summary>
    /// Гамма-коррекция.
    /// </summary>
    class Gamma : AbstractImageEffect
    {
        readonly float _gammaValue;
        int[] _gammCorrection;

        public Gamma(BitmapInfo4 bi, float gammaVal)
        {
            _bitmapInfo = bi;
            _gammaValue = gammaVal;
            PrepareGamma();
        }

        private void PrepareGamma()
        {
            _gammCorrection = new int[256];

            _gammCorrection[0] = 0;

            for (int i = 1; i < 256; i++)
            {
                _gammCorrection[i] = Convert.ToInt32(255 * Math.Pow(i / 255.0, _gammaValue));
            }
        }

        protected override int PixelEffect(int index)
        {
            int color = _bitmapInfo.ImageArray4[index];
            Utils.Unpack(color, out int a, out int r, out int g, out int b);

            return Utils.CutEdges(a, _gammCorrection[r], _gammCorrection[g], _gammCorrection[b]);
        }
    }
    #endregion

    #region === Emboss ===
    /// <summary>
    /// Гравюра.
    /// </summary>
    class Emboss : AbstractImageEffect
    {
        readonly int _shiftX;
        readonly int _shiftY;

        public Emboss(BitmapInfo4 bi, double angle, int shift)
        {
            _bitmapInfo = bi;

            _shiftX = Convert.ToInt32(shift * Math.Cos(angle));
            _shiftY = Convert.ToInt32(-shift * Math.Sin(angle));
        }

        protected override int PixelEffect(int index)
        {
            int color1 = _bitmapInfo.ImageArray4[index];
            Utils.Unpack(color1, out int _, out int r1, out int g1, out int b1);

            int line = _bitmapInfo.Stride / 4;

            int x = index % line;
            int y = index / line;

            int color2 = GetPixel(x + _shiftX, y + _shiftY);
            Utils.Unpack(color2, out int _, out int r2, out int g2, out int b2);

            int r = (255 - r2 + r1) / 2;
            int g = (255 - g2 + g1) / 2;
            int b = (255 - b2 + b1) / 2;

            int mean = Convert.ToByte(0.2125 * r + 0.7154 * g + 0.0721 * b);

            return Utils.Pack(0xFF, mean, mean, mean);
        }
    }
    #endregion

    #region === Filter ===
    /// <summary>
    /// Фильтрация изображения.
    /// </summary>
    class ImageFilter : AbstractImageEffect
    {
        readonly Matrix _matrix;

        public ImageFilter(BitmapInfo4 bi, Matrix matrix)
        {
            _bitmapInfo = bi;
            _matrix = matrix;
        }

        protected Matrix[] GetPixelMatrix(int index)
        {
            int line = _bitmapInfo.Stride / 4;

            int x = index % line;
            int y = index / line;

            Matrix[] matrixes = new Matrix[3];
            matrixes[0] = new Matrix(_matrix.Rows, _matrix.Columns);
            matrixes[1] = new Matrix(_matrix.Rows, _matrix.Columns);
            matrixes[2] = new Matrix(_matrix.Rows, _matrix.Columns);

            int nx = _matrix.Columns / 2;
            int ny = _matrix.Rows / 2;

            for (int xi = x - nx; xi <= x + nx; xi++)
            {
                for (int yi = y - ny; yi <= y + ny; yi++)
                {
                    int color = GetPixel(xi, yi);
                    Utils.Unpack(color, out int _, out int r, out int g, out int b);
                    matrixes[0].SetItem(xi - x + nx, yi - y + ny, Convert.ToSingle(r));
                    matrixes[1].SetItem(xi - x + nx, yi - y + ny, Convert.ToSingle(g));
                    matrixes[2].SetItem(xi - x + nx, yi - y + ny, Convert.ToSingle(b));
                }
            }

            return matrixes;
        }

        protected override int PixelEffect(int index)
        {
            Matrix[] colorMatrix = GetPixelMatrix(index);
            float[] channels = colorMatrix.Select(x => _matrix * x).ToArray();

            int color = _bitmapInfo.ImageArray4[index];

            int a = (color & (0xFF << 24)) >> 24;
            int r = Convert.ToInt32(channels[0]);
            int g = Convert.ToInt32(channels[1]);
            int b = Convert.ToInt32(channels[2]);

            return Utils.CutEdges(a, r, g, b);
        }
    }
    #endregion
}
